5.4 - Production- Training Units and Researching Upgrades
Once your base is established, your bot's primary goal is to spend its resources effectively by producing units and enhancing them with upgrades. The commands for training and researching are very similar: you select an eligible building and issue a specific production command.
The Production Request Lifecycle
Both training and researching follow the same logical flow. Before issuing a command, you must verify that the request is valid.
(You want to produce something...)
|
v
.--- IS IT POSSIBLE? ------.
| - self.can_afford(ITEM_ID)| <-- Do I have the resources?
| - self.supply_left > 0 | <-- (For units only) Do I have supply?
`---------------------------`
| (Yes)
v
.--- IS IT REDUNDANT? -----------------.
| - self.already_pending(ITEM_ID) == 0 | <-- Am I already making this??|
`--------------------------------------`
| (No)
v
.--- WHERE CAN I MAKE IT? ----------------------------.
| - building = self.structures(TYPE).ready.idle.first |
`-----------------------------------------------------`
| (Building exists)
v
.---- ISSUE COMMAND -----------.
| - building.train(UNIT) |
| - building.research(UPGRADE) |
`------------------------------`
Adhering to this lifecycle prevents your bot from getting stuck trying to afford something it can't, or wasting time queuing redundant upgrades.
A Comparison of Production Commands
Aspect | Training a Unit | Researching an Upgrade |
---|---|---|
Command | structure.train(unit_id) | structure.research(upgrade_id) |
Target Building | A production structure (Barracks, Gateway). | A tech structure (Forge, Engineering Bay). |
ID Type | UnitTypeId | UpgradeId |
Pre-checks | can_afford , supply_left | can_afford |
Redundancy Check | self.already_pending(unit_id) | self.already_pending_upgrade(upgrade_id) |
Finding a Building | self.structures(BARRACKS).ready.idle | self.structures(FORGE).ready.idle |
A Developer's Checklist for Production
Before you write any production logic, answer these questions:
- 1. What do I want to build? (e.g., a Marine)
- 2. Can I afford it? (
self.can_afford(UnitTypeId.MARINE)
) - 3. (For units) Do I have the supply? (
self.supply_left >= 1
) - 4. Am I already making it? (
self.already_pending(UnitTypeId.MARINE) == 0
) - 5. Do I have a building that can make it, and is it ready and idle? (
self.structures(UnitTypeId.BARRACKS).ready.idle
) - 6. If all checks pass, issue the command.
Code Example: The Quartermaster Bot
This bot acts as a quartermaster, managing both unit production and research requests. It uses clean, separate functions for each task and demonstrates the robust, query-based approach to finding eligible buildings.
# quartermaster_bot.py
from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.data import Difficulty, Race
from sc2.main import run_game
from sc2.player import Bot, Computer
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.upgrade_id import UpgradeId
class QuartermasterBot(BotAI):
"""A bot that manages production and research requests."""
async def on_step(self, iteration: int):
# We must have a command center to do anything.
if not self.townhalls:
return
# Core logic loop:
await self.manage_unit_production()
await self.manage_research()
# Helper to build our structures for the demo.
await self.build_tech_structures()
async def manage_unit_production(self):
"""Trains marines from any available idle barracks."""
# Find all idle, ready barracks.
idle_barracks = self.structures(UnitTypeId.BARRACKS).ready.idle
if not idle_barracks:
return
# Check conditions before issuing the command.
can_afford_marine = self.can_afford(UnitTypeId.MARINE)
has_enough_supply = self.supply_left >= 1
is_not_pending = self.already_pending(UnitTypeId.MARINE) == 0
if can_afford_marine and has_enough_supply and is_not_pending:
# Train from the first available barracks to prevent over-queuing.
await self.do(idle_barracks.first.train(UnitTypeId.MARINE))
async def manage_research(self):
"""Researches infantry weapons if possible."""
# Find all idle, ready engineering bays.
idle_eng_bays = self.structures(UnitTypeId.ENGINEERINGBAY).ready.idle
if not idle_eng_bays:
return
# Define the upgrade chain
upgrades = [
UpgradeId.TERRANINFANTRYWEAPONSLEVEL1,
UpgradeId.TERRANINFANTRYWEAPONSLEVEL2,
UpgradeId.TERRANINFANTRYWEAPONSLEVEL3
]
for upgrade in upgrades:
# Check conditions for the current upgrade level.
can_afford_upgrade = self.can_afford(upgrade)
is_not_pending = self.already_pending_upgrade(upgrade) == 0
# Ensure we don't research an already completed level.
is_not_finished = upgrade not in self.state.upgrades
if can_afford_upgrade and is_not_pending and is_not_finished:
# Research from the first available bay and stop checking.
await self.do(idle_eng_bays.first.research(upgrade))
break
async def build_tech_structures(self):
"""A simple helper to build the necessary structures for the demo."""
if self.supply_left < 3 and self.already_pending(UnitTypeId.SUPPLYDEPOT) == 0:
if self.can_afford(UnitTypeId.SUPPLYDEPOT):
await self.build(UnitTypeId.SUPPLYDEPOT, near=self.start_location.towards(self.game_info.map_center, 5))
if self.structures(UnitTypeId.SUPPLYDEPOT).ready:
if not self.structures(UnitTypeId.BARRACKS) and not self.already_pending(UnitTypeId.BARRACKS):
if self.can_afford(UnitTypeId.BARRACKS):
await self.build(UnitTypeId.BARRACKS, near=self.start_location.towards(self.game_info.map_center, 8))
if self.structures(UnitTypeId.BARRACKS).ready:
if not self.structures(UnitTypeId.ENGINEERINGBAY) and not self.already_pending(UnitTypeId.ENGINEERINGBAY):
if self.can_afford(UnitTypeId.ENGINEERINGBAY):
await self.build(UnitTypeId.ENGINEERINGBAY, near=self.start_location.towards(self.game_info.map_center, 10))
if __name__ == "__main__":
run_game(
maps.get("BlackburnAIE"),
[
Bot(Race.Terran, QuartermasterBot()),
Computer(Race.Zerg, Difficulty.VeryEasy)
],
realtime=True,
)